#ifndef __INCLUDED_CBL_MATRIX_H__
#error "CBL_Matrix.hpp must not be used directly. Use CBL_Matrix.h instead."
#else
#ifndef __INCLUDED_CBL_MATRIX_HPP__
#define __INCLUDED_CBL_MATRIX_HPP__

#define MATRIX_TYPE CMatrix<TType, TMajorSize, TMinorSize>

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
MATRIX_TYPE::CMatrix()
{
	SetIdentity(*this);
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
MATRIX_TYPE::CMatrix(const TType* _pData)
{
	*this = _pData;
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
template<typename TType2, uint32 TMajorSize2, uint32 TMinorSize2>
MATRIX_TYPE::CMatrix(const CMatrix<TType2, TMajorSize2, TMinorSize2>& _rOther)
{
	memset(m_Values, 0, s_MemSize); 
	*this = _rOther;
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE void CB_INLINE_ATTR MATRIX_TYPE::operator = (const TType* _pData)
{
	memcpy(m_Values, _pData, s_MemSize);
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
template <typename TType2, uint32 TMajorSize2, uint32 TMinorSize2>
CB_FORCEINLINE void CB_INLINE_ATTR MATRIX_TYPE::operator = (const CMatrix<TType2, TMajorSize2, TMinorSize2>& _rOther)
{
	typedef Meta::Min<TMinorSize,TMinorSize2> MinMinorSize;
	typedef Meta::Min<TMajorSize,TMajorSize2> MinMajorSize;
	
	for (uint32 mj(0u); mj<MinMajorSize::Result; ++mj)
	{
		for (uint32 mn(0u); mn<MinMinorSize::Result; ++mn)
		{
			m_Values[mj][mn] = static_cast<TType>(_rOther[mj][mn]);
		}
	}
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE TType* CB_INLINE_ATTR MATRIX_TYPE::operator [] (uint32 _Index)
{
	return &reinterpret_cast<TType*>(m_Values)[_Index * TMinorSize];
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE const TType* CB_INLINE_ATTR MATRIX_TYPE::operator [] (uint32 _Index) const
{
	return &reinterpret_cast<const TType*>(m_Values)[_Index * TMinorSize];
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE TType* CB_INLINE_ATTR MATRIX_TYPE::Array()
{
	return reinterpret_cast<TType*>(m_Values);
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE const TType* CB_INLINE_ATTR MATRIX_TYPE::Array() const
{
	return reinterpret_cast<const TType*>(m_Values);
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
template <typename TType2, uint32 TMinorSize2>
CB_FORCEINLINE MATRIX_TYPE& CB_INLINE_ATTR MATRIX_TYPE::operator *= (const CMatrix<TType2, TMinorSize, TMinorSize2>& _rOther)
{
	Debug::Assert(TMajorSize <= TMinorSize2);
	MATRIX_TYPE Base = *this;
	
	for (uint32 mj(0u); mj<TMajorSize; ++mj)
	{
		for (uint32 mn(0u); mn<TMinorSize; ++mn)
		{
			m_Values[mj][mn] = 0;
			for (uint32 i(0u); i<TMinorSize; ++i)
			{
#ifdef CBL_MATRIX_COL_MAJOR
				m_Values[mj][mn] += Base[i][mj] * _rOther[mn][i];
#else
				m_Values[mj][mn] += Base[mj][i] * _rOther[i][mn];
#endif
			}
		}
	}
	
	return *this;
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE MATRIX_TYPE& CB_INLINE_ATTR MATRIX_TYPE::operator *= (const TType& _rOther)
{
	for (uint32 mj(0u); mj<TMinorSize; ++mj)
	{
		for (uint32 mn(0u); mn<TMajorSize; ++mn)
		{
			m_Values[mj][mn] *= _rOther;
		}
	}
	
	return *this;
}

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE bool CB_INLINE_ATTR MATRIX_TYPE::AlmostEq (const MATRIX_TYPE& _rOther, float _Epsilon)
{
	for (uint32 mj(0u); mj<TMinorSize; ++mj)
	{
		for (uint32 mn(0u); mn<TMajorSize; ++mn)
		{
			if (Abs(m_Values[mj][mn] - _rOther[mj][mn]) > _Epsilon) 
				return false;
		}
	}
	
	return true;
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TLhsType, typename TRhsType, uint32 TLhsMajorSize, uint32 TSharedSize, uint32 TRhsMinorSize>
CB_FORCEINLINE CMatrix<TLhsType, TLhsMajorSize, TSharedSize> CB_INLINE_ATTR operator * (CMatrix<TLhsType, TLhsMajorSize, TSharedSize> _Lhs, const CMatrix<TRhsType, TSharedSize, TRhsMinorSize>& _rRhs)
{
	return _Lhs *= _rRhs;
}

// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE std::ostream& CB_INLINE_ATTR operator << (std::ostream& _rLhs, MATRIX_TYPE& _rRhs)
{
	for (int mj(0); mj<TMajorSize; ++mj)
	{
		_rLhs << "[";
		for (int mn(0); mn<TMinorSize; ++mn)
		{
			if (mn > 0) _rLhs << ", ";
			_rLhs << _rRhs[mj][mn];
		}
		
		_rLhs << "]";
		if (mj<TMajorSize-1) _rLhs << std::endl;
	}
	
	return _rLhs;
}


// ---------------------------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE void CB_INLINE_ATTR SetIdentity(MATRIX_TYPE& _rMatrix)
{
	memset(_rMatrix.Array(), 0, _rMatrix.s_MemSize);
	for (uint32 i(0u); (i<TMajorSize) && (i<TMinorSize); ++i)
	{
		_rMatrix[i][i] = 1;
	}
}

// -------------------------------------------------------------------------------------------------

template <typename TType, uint32 TMajorSize, uint32 TMinorSize>
CB_FORCEINLINE MATRIX_TYPE CB_INLINE_ATTR Transpose(const MATRIX_TYPE& _rMtx)
{
	MATRIX_TYPE MtxT;
	
	for (uint32 mj(0u); mj<TMinorSize; ++mj)
	{
		for (uint32 mn(0u); mn<TMajorSize; ++mn)
		{
			MtxT[mn][mj] = _rMtx[mj][mn];
		}
	}
	
	return MtxT;
}

// -------------------------------------------------------------------------------------------------

template <typename TType, uint32 TSize>
CB_FORCEINLINE CMatrix<TType, TSize-1, TSize-1> CB_INLINE_ATTR Split(const CMatrix<TType, TSize, TSize>& _rMtx, uint32 _AtMajor, uint32 _AtMinor)
{
	CMatrix<TType, TSize-1, TSize-1> SubMtx;
	
	uint32 MjSub = 0;
	for (uint32 mj(0); mj<TSize; ++mj)
	{
		if (mj != _AtMajor)
		{
			uint32 MnSub = 0;
			for (uint32 mn(0); mn<TSize; ++mn)
			{
				if (mn != _AtMinor)
				{
					SubMtx[MjSub][MnSub] = _rMtx[mj][mn];
					++MnSub;
				}
			}
			++MjSub;
		}
	}
	
	return SubMtx;
}

// -------------------------------------------------------------------------------------------------

template <typename TType, uint32 TSize>
struct _DetOp
{
	CB_FORCEINLINE TType CB_INLINE_ATTR operator() (const CMatrix<TType, TSize, TSize>& _rMtx)
	{
		TType Det = 0;
		TType Sign = 1;

		for (uint32 i(0); i<TSize; ++i)
		{
			// Det(M) == Det(M_T) -> no col/row major dependency
			CMatrix<TType, TSize-1, TSize-1> SubMtx = Split(_rMtx, 0, i);
			Det += Sign * _rMtx[0][i] * Determinant(SubMtx);
			Sign *= -1;
		}

		return Det;
	}
};

template <typename TType>
struct _DetOp<TType, 2>
{
	CB_FORCEINLINE TType CB_INLINE_ATTR operator() (const CMatrix<TType, 2, 2>& _rMtx)
	{
		return _rMtx[0][0] * _rMtx[1][1] - _rMtx[1][0] * _rMtx[0][1];
	}
};

template <typename TType>
struct _DetOp<TType, 1>
{
	CB_FORCEINLINE TType CB_INLINE_ATTR operator() (const CMatrix<TType, 1, 1>& _rMtx)
	{
		return _rMtx[0][0];
	}
};

template <typename TType, uint32 TSize>
CB_FORCEINLINE TType CB_INLINE_ATTR Determinant(const CMatrix<TType, TSize, TSize>& _rMtx)
{
	_DetOp<TType, TSize> Functor;
	return Functor(_rMtx);
}

// -------------------------------------------------------------------------------------------------

template <typename TType, uint32 TSize>
CB_FORCEINLINE bool CB_INLINE_ATTR IsInverible(const CMatrix<TType, TSize, TSize>& _rMtx)
{
	return Determinant(_rMtx) != 0;
}

// -------------------------------------------------------------------------------------------------

template <typename TType, uint32 TSize>
CB_FORCEINLINE CMatrix<TType, TSize, TSize> CB_INLINE_ATTR Invert(const CMatrix<TType, TSize, TSize>& _rMtx)
{
	CMatrix<TType, TSize, TSize> MtxI;
	
	TType Det = Determinant(_rMtx);
	if (Det != 0)
	{
		TType MjSign = 1;
		for (uint32 mj(0); mj<TSize; ++mj)
		{
			TType Sign = MjSign;
			for (uint32 mn(0); mn<TSize; ++mn)
			{
				MtxI[mj][mn] = Sign * Determinant(Split(_rMtx, mn, mj));
				Sign *= -1;
			}
			MjSign *= -1;
		}
		
		MtxI *= 1/Det;
	}
	
	return MtxI;
}

// -------------------------------------------------------------------------------------------------

CB_FORCEINLINE Float4x4 CB_INLINE_ATTR Translation3D(float _X, float _Y, float _Z)
{
	Float4x4 Mtx;
	Mtx[3][0] = _X;
	Mtx[3][1] = _Y;
	Mtx[3][2] = _Z;
	return Mtx;
}

// -------------------------------------------------------------------------------------------------

CB_FORCEINLINE Float4x4 CB_INLINE_ATTR Scale3D(float _X, float _Y, float _Z)
{
	Float4x4 Mtx;
	Mtx[0][0] = _X;
	Mtx[1][1] = _Y;
	Mtx[2][2] = _Z;
	return Mtx;
}

// -------------------------------------------------------------------------------------------------

CB_FORCEINLINE Float4x4 CB_INLINE_ATTR Rotation3D(float _XDeg, float _YDeg, float _ZDeg)
{
	float SinP = sin(Rad(_XDeg));
	float SinH = sin(Rad(_YDeg));
	float SinR = sin(Rad(_ZDeg));
	
	float CosP = cos(Rad(_XDeg));
	float CosH = cos(Rad(_YDeg));
	float CosR = cos(Rad(_ZDeg));
	
	return Float4x4(CosR*CosH-SinR*SinP*SinH,	-SinR*CosP,		CosR*SinH+SinR*SinP*CosH,	0,
					SinR*CosH+CosR*SinP*SinH,	CosR*CosP,		SinR*SinH-CosR*SinP*CosH,	0,
					-CosP*SinH,					SinP,			CosP*CosH,					0,
					0,							0,				0,							1);
}

// -------------------------------------------------------------------------------------------------

CB_FORCEINLINE Float3x3 CB_INLINE_ATTR Translation2D(float _X, float _Y)
{
	Float3x3 Mtx;
	Mtx[2][0] = _X;
	Mtx[2][1] = _Y;
	return Mtx;
}

// -------------------------------------------------------------------------------------------------

CB_FORCEINLINE Float3x3 CB_INLINE_ATTR Scale2D(float _X, float _Y)
{
	Float3x3 Mtx;
	Mtx[0][0] = _X;
	Mtx[1][1] = _Y;
	return Mtx;
}

// -------------------------------------------------------------------------------------------------

CB_FORCEINLINE Float3x3 CB_INLINE_ATTR Rotation2D(float _Deg)
{
	Float3x3 Mtx;
	float SinDeg = sin(_Deg);
	float CosDeg = cos(_Deg);
	
	Mtx[0][0] = CosDeg; Mtx[0][1] = -SinDeg;
	Mtx[1][0] = SinDeg; Mtx[1][1] = CosDeg;
	
	return Mtx;
}

#undef MATRIX_TYPE

#endif
#endif